// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/bittrex/api_order/native_private_client.h"

#include "coin2/exchange/bittrex/api_util/auth_key.h"

#define CHECK_API_LIMIT(action_type, ep)                                                  \
  const std::string endpoint(ep);                                                         \
  auto ts = GetCurrentTimestamp();                                                        \
  if (!HasEnoughRateLimit(ts, action_type)) {                                             \
    LOG_EVERY_N(WARNING, 50) << "Hit rate limit or api temporary disabled, " << __func__; \
    api_block_info_[__func__]++;                                                          \
    return {};                                                                            \
  }                                                                                       \
  local_rate_tracker_->Push(1, ts, action_type);                                          \
  api_req_info_[__func__]++;

namespace coin2::exchange::bittrex::api_order {

BittrexNativePrivateClient::BittrexNativePrivateClient(
    const MarketExchangeApi& mea,
    const NativeClientConfig& config_override) {
  mea_ = mea;
  NativeClientConfig config;
  config.set_rest_host("api.bittrex.com");
  config.set_rest_host_port(443);
  config.MergeFrom(config_override);
  LOG(INFO) << "[BittrexNativePrivateClient] config: " << config.DebugString();

  auths_.Construct<BittrexRestAuth, NativeClientConfig>(config);

  rate_tracker_ = std::make_unique<BittrexRateTracker>(mea_, config);
  local_rate_tracker_ = dynamic_cast<BittrexRateTracker*>(rate_tracker_.get());
  start_time_ = GetCurrentTimestamp();
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::QueryAccountBalance() {
  CHECK_API_LIMIT(ActionType::QUERY, "/v3/balances");

  UrlParamsBuilder builder;
  auto* context = GetAuth()->CreateRequestByGetWithSignature(endpoint, &builder);

  context->query_type = OrderSubTopicId::REST_ACCOUNT_BALANCE;
  context->extra_info = DebugString();
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::QueryOpenOrders() {
  CHECK_API_LIMIT(ActionType::QUERY, "/v3/orders/open");

  UrlParamsBuilder builder;
  auto* context = GetAuth()->CreateRequestByGetWithSignature(endpoint, &builder);

  context->query_type = OrderSubTopicId::REST_OPEN_ORDERS;
  context->extra_info = DebugString();
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::QueryHistoryOrders() {
  CHECK_API_LIMIT(ActionType::QUERY, "/v3/orders/closed");

  UrlParamsBuilder builder;
  builder.PutGet("pageSize", 200);
  auto* context = GetAuth()->CreateRequestByGetWithSignature(endpoint, &builder);

  context->query_type = OrderSubTopicId::REST_DONE_ORDERS;
  context->extra_info = DebugString();
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::QueryFills() {
  CHECK_API_LIMIT(ActionType::QUERY, "/v3/executions");

  UrlParamsBuilder builder;
  builder.PutGet("pageSize", 200);
  auto* context = GetAuth()->CreateRequestByGetWithSignature(endpoint, &builder);

  context->query_type = OrderSubTopicId::REST_FILL_ORDERS;
  context->extra_info = DebugString();
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::GenListenKey(bool is_init) {
  if (is_init && !listen_key_.empty()) {
    return nullptr;
  }

  // https://socket-v3.bittrex.com//signalr/negotiate?clientProtocol=2.0&connectionData=%5B%7B%22name%22%3A%22c3%22%7D%5D

  UrlParamsBuilder builder;

  builder.PutGet("clientProtocol", "2.0");
  builder.PutGet("connectionData", "%5B%7B%22name%22%3A%22c3%22%7D%5D");

  auto context = std::shared_ptr<AsioHttpContext>(
      static_cast<BittrexRestAuth*>(GetAuth())->CreateRequestByGetWithSignature(
          "//signalr/negotiate",
          &builder,
          "socket-v3.bittrex.com"));
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::LISTEN_KEY;
  context->extra_info = DebugString();
  return context;
}

void BittrexNativePrivateClient::Prepare() {
  mb_->Subscribe("bittrex/enable_time", [this](const std::string&, void* data) {
    int64_t value = reinterpret_cast<int64_t>(data);
    local_rate_tracker_->UpdateEnableTime(value);
    LOG(WARNING) << "Bittrex native client enable_time: " << value;
  });
  mb_->Subscribe("bittrex/listen_key", [this](const std::string&, void* data) {
    std::string listen_key = *reinterpret_cast<std::string*>(data);

    this->listen_key_ = listen_key;

    LOG(INFO) << "Bittrex native client set listen_key: " << listen_key;
  });
}

std::string BittrexNativePrivateClient::DebugString() const {
  double delta_t = (GetCurrentTimestamp() - start_time_) / 1.0e9;
  std::stringstream ss;
  ss << "BittrexNativePrivateClient::DebugString\n";
  ss << "api info\n";
  for (auto& [name, value] : api_req_info_) {
    int32_t blocked = 0;
    if (api_block_info_.count(name) > 0) {
      blocked = api_block_info_.at(name);
    }
    int32_t total_req = value + blocked;
    ss << name << " (total:" << total_req << ", succeeded: " << value << ", blocked: " << blocked
       << ", req_rate: " << value / delta_t << ", block_rate: " << blocked / delta_t << ")\n";
  }
  return ss.str();
}

std::string BittrexNativePrivateClient::GetUUIDByProcOrderId(int64_t proc_order_id) {
  // format as uuid "c0aa9386-3431-4cd9-ae50-1295ff5e9a8c"
  constexpr static auto divider_12_digits = 10000'0000'0000LL;
  constexpr static auto divider_4_digits = 10000LL;

  auto last_12_digits = proc_order_id % divider_12_digits;
  proc_order_id /= divider_12_digits;

  auto mid_4_digits = proc_order_id % divider_4_digits;
  auto head_4_digits = proc_order_id / divider_4_digits;

  return fmt::format(
      "00000000-0000-{:04}-{:04}-{:012}",
      head_4_digits,
      mid_4_digits,
      last_12_digits);
}

auto BittrexNativePrivateClient::GetRestOrderParams(
    const OrderSpec& order_spec,
    int64_t proc_order_id) -> RestParams {
  auto params = RestParams();
  BittrexProduct product =
      BittrexProduct::FromStr(order_spec.product, order_spec.order_created_time);
  auto product_holder = GetProductHolder(product, order_spec.order_created_time);
  params.set_market_symbol(order_spec.product);
  params.set_client_order_id(GetUUIDByProcOrderId(proc_order_id));

  if (order_spec.order_side == OrderSide::BUY_ORDER) {
    params.set_direction("BUY");
  } else if (order_spec.order_side == OrderSide::SELL_ORDER) {
    params.set_direction("SELL");
  } else {
    NOTREACHED() << "Unknown order side: " << static_cast<int>(order_spec.order_side);
  }

  if (order_spec.post_only) {
    params.set_time_in_force("POST_ONLY_GOOD_TIL_CANCELLED");
  } else if (order_spec.duration == coin::proto::OrderDuration::IOC_ORDER) {
    params.set_time_in_force("IMMEDIATE_OR_CANCEL");
  } else if (order_spec.duration == coin::proto::OrderDuration::FOK_ORDER) {
    params.set_time_in_force("FILL_OR_KILL");
  } else {
    params.set_time_in_force("GOOD_TIL_CANCELLED");
  }

  auto formatter = product_holder->product_info().order_formatter();
  params.set_quantity(formatter.FormatQty(order_spec.qty));
  if (order_spec.order_type == OrderType::LIMIT_ORDER) {
    params.set_limit(formatter.FormatPrice(order_spec.price));
    params.set_type("LIMIT");
  } else if (order_spec.order_type == OrderType::MARKET_ORDER) {
    params.set_type("MARKET");
  } else {
    NOTREACHED() << "Unknown order type: " << static_cast<int>(order_spec.order_type);
  }

  return params;
}

auto BittrexNativePrivateClient::GetRestCancelParams(const IOrderContext& order_context)
    -> RestParams {
  auto params = RestParams();

  params.set_id(order_context.exchange_order_id());

  return params;
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::PlaceOrder(const RestParams& params) {
  CHECK_API_LIMIT(ActionType::PLACE, "/v3/orders");

  UrlParamsBuilder builder;
  builder.PutPost("marketSymbol", params.market_symbol());
  builder.PutPost("direction", params.direction());
  builder.PutPost("type", params.type());
  builder.PutPost("quantity", params.quantity());
  builder.PutPost("limit", params.limit());
  builder.PutPost("timeInForce", params.time_in_force());
  builder.PutPost("clientOrderId", params.client_order_id());
  auto* context = GetAuth()->CreateRequestByPostWithSignature(endpoint, &builder);

  context->query_type = OrderSubTopicId::SUBMIT_RESPONSE;
  context->extra_info = DebugString();
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::CancelOrder(const RestParams& params) {
  CHECK_API_LIMIT(ActionType::CANCEL, "/v3/orders/" + params.id());

  UrlParamsBuilder builder;
  auto* context = GetAuth()->CreateRequestByDeleteWithSignature(endpoint, &builder);

  context->query_type = OrderSubTopicId::CANCEL_RESPONSE;
  context->extra_info = DebugString();
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::PlaceOrderNorm(
    const OrderExecution& order_exe) {
  const auto& order_spec = *order_exe.order_spec;
  const auto proc_order_id = order_exe.proc_order_id;
  auto params = GetRestOrderParams(order_spec, proc_order_id);
  return PlaceOrder(params);
}

std::shared_ptr<AsioHttpContext> BittrexNativePrivateClient::CancelOrderNorm(
    const OrderExecution& order_exe) {
  auto params = GetRestCancelParams(*order_exe.oc);
  return CancelOrder(params);
}

}  // namespace coin2::exchange::bittrex::api_order
